package com.anti.logging;

import cn.hutool.json.JSONUtil;
import com.anti.commom.core.exception.BusinessException;
import com.anti.commom.core.utils.RequestUtils;
import com.anti.commom.annotation.BusinessLog;
import com.anti.logging.model.RequestErrorInfo;
import com.anti.logging.model.RequestInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class LogAspect {
    private final static Logger logger = LoggerFactory.getLogger(LogAspect.class);

    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(com.anti.commom.annotation.BusinessLog)")
    public void requestServer() {
    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     */
    @Pointcut("execution(* com.anti..*.controller..*.*(..))")
    public void operExceptionLogPoinCut() {}

    /**
     * 正常返回通知，拦截用户操作日志，连接点正常执行完成后执行， 如果连接点抛出异常，则不会执行
     * @param joinPoint 切入点
     * @param keys 返回结果
     */
    @AfterReturning(value = "requestServer()", returning = "keys")
    public void afterReturning(JoinPoint joinPoint, Object keys) {
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取操作
        BusinessLog apiLog = method.getAnnotation(BusinessLog.class);
        RequestInfo requestInfo = new RequestInfo();
        if (apiLog != null) {
            String module = apiLog.title();// 操作模块
            String type = apiLog.opType().name(); // 操作类型
            requestInfo.setModule(module);
            requestInfo.setType(type);
        }
        HttpServletRequest request = attributes.getRequest();
        requestInfo.setIp(request.getRemoteAddr());
        requestInfo.setUrl(request.getRequestURL().toString());
        requestInfo.setHttpMethod(request.getMethod());
        requestInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(),joinPoint.getSignature().getName()));
        requestInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
        requestInfo.setResult(keys);
        requestInfo.setTimeCost(System.currentTimeMillis() - start);
        logger.info("Request Info      : {}", JSONUtil.toJsonStr(requestInfo));
    }

    /**
     * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     * @param joinPoint 切入点
     * @param e 异常信息
     */
    @AfterThrowing(pointcut = "operExceptionLogPoinCut()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, Throwable e) {
        if (!(e instanceof BusinessException)){
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            RequestErrorInfo requestErrorInfo = new RequestErrorInfo();
            requestErrorInfo.setIp(RequestUtils.getRemoteIP(request));
            requestErrorInfo.setUrl(request.getRequestURL().toString());
            requestErrorInfo.setHttpMethod(request.getMethod());
            requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(),joinPoint.getSignature().getName()));
            requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
            requestErrorInfo.setExcName(e.getClass().getName());
            requestErrorInfo.setExcMessage(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace())); // 异常信息
            requestErrorInfo.setUserId(null);
            requestErrorInfo.setUserName(null);
            requestErrorInfo.setCreateTime(new Date());
            logger.error("Error Request Info      : {}", JSONUtil.toJsonStr(requestErrorInfo));
        }
    }


    private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = joinPoint.getArgs();

        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<String, Object>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            //如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();  //获取文件名
            }
            requestParams.put(paramNames[i], value);
        }

        return requestParams;
    }

    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        return message;
    }
}
